/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package uk.co.dupenois.geometry;

import java.util.Collections;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.ArrayList;

/**
 *
 * @author mpd209
 */
public class CoordinateList {
    private TreeSet<Coordinate> pointsByX;
    private TreeSet<Coordinate> pointsByY;
    private TreeSet<Coordinate> pointsByXY;
    private TreeSet<Coordinate> pointsByMinXMaxY;

    private double meanX;
    private double meanY;
    
    public CoordinateList(Coordinate[] coordinates){
        initialiseSets();
        for(Coordinate c : coordinates){
            pointsByX.add(c);
            pointsByY.add(c);
            pointsByXY.add(c);
            pointsByMinXMaxY.add(c);
            meanX += c.getX();
            meanY += c.getY();
        }
        meanX = meanX/coordinates.length;
        meanY = meanY/coordinates.length;
    }
    public CoordinateList(ArrayList<Coordinate> coordinates){
        initialiseSets();
        for(Coordinate c : coordinates){
            pointsByX.add(c);
            pointsByY.add(c);
            pointsByXY.add(c);
            pointsByMinXMaxY.add(c);
            meanX += c.getX();
            meanY += c.getY();
        }
        meanX = meanX/coordinates.size();
        meanY = meanY/coordinates.size();
    }
    private void initialiseSets(){
        pointsByX = new TreeSet<Coordinate>(Coordinate.MinXMinYComparator);
        pointsByY = new TreeSet<Coordinate>(Coordinate.MinYMinXComparator);
        pointsByXY = new TreeSet<Coordinate>(Coordinate.XYComparator);
        pointsByMinXMaxY = new TreeSet<Coordinate>(Coordinate.MinXMaxYComparator);
    }

    public int size(){
        return pointsByX.size();
    }

//    public Coordinate getFromXList(int index){
//        return pointsByX.;
//    }
//
//    public Coordinate getFromYList(int index){
//        return pointsByY.get(index);
//    }

    public synchronized Iterator<Coordinate> xIterator(){
        return pointsByX.iterator();
    }


    public synchronized Iterator<Coordinate> yIterator(){
        return pointsByY.iterator();
    }

    public synchronized Iterator<Coordinate> xyIterator(){
        return pointsByXY.iterator();
    }
     public synchronized Iterator<Coordinate> minXMaxYIterator(){
        return pointsByMinXMaxY.iterator();
    }


//    private void sortByX(Vector<Coordinate> points){
//        Collections.sort(points, new Comparator<Coordinate>() {
//            public int compare(Coordinate arg0, Coordinate arg1) {
//                double diff = arg0.getX() - arg1.getX();
//                if(diff == 0){
//                    diff = arg1.getY() - arg0.getY();
//                    if(diff == 0) return 0;
//                    if(diff<0) return -1;
//                    return 1;
//                }
//                if(diff<0)return -1;
//                return 1;
//            }
//        });
//    }
//    private void sortByY(Vector<Coordinate> points){
//        Collections.sort(points, new Comparator<Coordinate>() {
//            public int compare(Coordinate arg0, Coordinate arg1) {
//                double diff = arg0.getY() - arg1.getY();
//                if(diff == 0){
//                    diff = arg1.getX() - arg0.getX();
//                    if(diff == 0) return 0;
//                    if(diff<0) return -1;
//                    return 1;
//                }
//                if(diff<0)return -1;
//                return 1;
//            }
//        });
//    }
//    private void sortByXY(Vector<Coordinate> points){
//        Collections.sort(points, new Comparator<Coordinate>() {
//            public int compare(Coordinate arg0, Coordinate arg1) {
//                double diff = (Math.pow(arg0.getX(), 2)+Math.pow(arg0.getY(), 2))-
//                             (Math.pow(arg1.getX(), 2)+Math.pow(arg1.getY(), 2));
//                if(diff == 0)return 0;
//                if(diff<0)return -1;
//                return 1;
//            }
//        });
//    }
//    private void sortByMinXMaxY(Vector<Coordinate> points){
//        Collections.sort(points, new Comparator<Coordinate>() {
//            public int compare(Coordinate arg0, Coordinate arg1) {
//                Coordinate topLeft = getBoxTopLeft();
//                double dist1 = Math.pow((arg0.getX()-topLeft.getX()), 2)+
//                        Math.pow((arg0.getY()-topLeft.getY()), 2);
//                double dist2 = Math.pow((arg1.getX()-topLeft.getX()), 2)+
//                        Math.pow((arg1.getY()-topLeft.getY()), 2);
//                double diff = dist1 - dist2;
//                if(diff == 0)return 0;
//                if(diff<0)return -1;
//                return 1;
////
////                double ydiff = arg0.getY() - arg1.getY();
////                double xdiff = arg0.getX() - arg1.getX();
////                if(xdiff<0 && ydiff>0)return -1;
////                if(xdiff==0 && ydiff==0)return 0;
////                if(xdiff>0 && ydiff<0)return 1;
////                if(xdiff<0 && ydiff<0)return 1;
////
//////                if(xdiff+ydiff<0)return -1;
//////                if(xdiff+ydiff==0)return 0;
////                return 1;
//
//            }
//        });
//    }
    public Coordinate getBoxTopLeft(){
        return new Coordinate(getFirstFromX().getX(), getLastFromY().getY());
    }
    public Coordinate getBoxTopRight(){
        return new Coordinate(getLastFromX().getX(), getLastFromY().getY());
    }
    public Coordinate getBoxBottomRight(){
        return new Coordinate(getLastFromX().getX(), getFirstFromY().getY());
    }
    public Coordinate getBoxBottomLeft(){
        return new Coordinate(getFirstFromX().getX(), getFirstFromY().getY());
    }
    public double getMinXValue(){
        return getFirstFromX().getX();
    }
    public double getMaxYValue(){
        return getLastFromY().getY();
    }
    public synchronized Coordinate getFirstFromX(){
        return pointsByX.first();
    }
    public synchronized Coordinate getFirstFromY(){
        return pointsByY.first();
    }
    public synchronized Coordinate getLastFromX(){
        return pointsByX.last();
    }
    public synchronized Coordinate getLastFromY(){
        return pointsByY.last();
    }
    public synchronized TreeSet<Coordinate> getByX(){
        return pointsByX;
    }
    public synchronized TreeSet<Coordinate> getByY(){
        return pointsByY;
    }

    public Line getLineOfBestFit(){
        /* Using Ordinary Least Squares
         * f(x) = ax + b
         * S : Sum vertical squared residuals
         * S = sum^n_i=1(y_i -(ax_i +b))^2
         * Must satisfy derivative dS/da = 0
         * and dS/sb = 0 
         * 
         * a = sum((x_i - mean(x))(y_i - mean(y)))/sum(x_i - mean(x))^2
         * b = mean(y) - a*mean(x)
         */
        //top = sum((x_i - mean(x))(y_i - mean(y)))
        //bottom = sum(x_i - mean(x))
        double a = 0;
        double b = 0;
        synchronized(this){
            Iterator<Coordinate> iter = pointsByX.iterator();
            Coordinate ci;
            double top = 0;
            double bottom = 0;
            double xi;
            double yi;
            while(iter.hasNext()){
                ci = iter.next();
                xi = ci.getX();
                yi = ci.getY();
                top += (xi - meanX)*(yi-meanY);
                bottom += (xi - meanX);
            }
            a = top/bottom;
            b = meanY - a*meanX;
        }
        Coordinate origin = new Coordinate(0, 0);
        Coordinate end = new Coordinate(meanX, (meanX*a + b));
        return new Line(origin, end, true);
    }
    
    public synchronized Coordinate getMidpoint(TreeSet<Coordinate> set){
        boolean even = (set.size()%2==0);
        int halfIndex = (even?set.size()/2:(int)Math.ceil((double)set.size()/2d));
        Iterator<Coordinate> iter = set.iterator();
        Coordinate mid = null;
        Coordinate current = null;
        Coordinate next = null;
        int count = 0;
        while(mid==null&&iter.hasNext()){
            current = iter.next();
            if(count==halfIndex){
                if(even){
                    next = iter.next();
                    mid = new Coordinate((next.getX()+current.getX())/2d,
                                        (next.getY()+current.getY())/2d);
                }else{
                    mid = current;
                }
            }
            count++;
        }
        return mid;
    }

    public synchronized CoordinateList[] splitByX(){
        CoordinateList[] arr = new CoordinateList[2];
        ArrayList<Coordinate> c1 = new ArrayList<Coordinate>();
        ArrayList<Coordinate> c2 = new ArrayList<Coordinate>();
        Coordinate midpoint = getMidpoint(pointsByX);
        c1.addAll(pointsByX.headSet(midpoint));
        c2.addAll(pointsByX.tailSet(midpoint));
        arr[0] = new CoordinateList(c1);
        arr[1] = new CoordinateList(c2);
        return arr;
    }
    public synchronized CoordinateList[] splitByY(){
        CoordinateList[] arr = new CoordinateList[2];
        ArrayList<Coordinate> c1 = new ArrayList<Coordinate>();
        ArrayList<Coordinate> c2 = new ArrayList<Coordinate>();
        Coordinate midpoint = getMidpoint(pointsByY);
        c1.addAll(pointsByY.headSet(midpoint));
        c2.addAll(pointsByY.tailSet(midpoint));
        arr[0] = new CoordinateList(c1);
        arr[1] = new CoordinateList(c2);
        return arr;
    }
}
